package com.korustar.autumnal.codemodel;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Properties;

import org.slf4j.LoggerFactory;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.factory.config.TypedStringValue;

import com.korustar.autumnal.reflect.ClassLoaderUtil;
import com.korustar.autumnal.spring.PropertyPlaceholderResolution;
import com.korustar.autumnal.spring.PropertyPlaceholderResolution.Property;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;

/**
 * Resolver for Spring's PropertyPlaceholderConfigurer
 * 
 * @author amir
 */
public class PropertyPlaceholderHelper {
	private static org.slf4j.Logger LOGGER = LoggerFactory
			.getLogger(PropertyPlaceholderHelper.class);

	private final PropertyPlaceholderResolution propertyPlaceholderResolution = new PropertyPlaceholderResolution();

	JExpression buildPropertyGetterFromProperties(String valueBefore,
			Class<?> expectedType, CodeModelContainerModel model) {
		List<Object> parts= this.propertyPlaceholderResolution.serialize(valueBefore);
		JExpression fullExp= null;
		boolean first=true;
		for(Object part : parts) {
			if(first) { 
				//first part can be an unnecessary empty string .. 
				if("".equals(part) && parts.size()>1) {
					continue;
				}
				first=false;
			}
			JExpression exp=null;
			if(part instanceof Property) { 
				Property prop= (Property)part;
				String propertyName = prop.propertyName;
				exp= JExpr.direct("beanHolder.properties.getProperty(\""+propertyName+"\")");
			} else {
				exp= JExpr.lit((String) part);
			}
			if(fullExp!=null) {
				fullExp= fullExp.plus(exp);
			} else {
				fullExp= exp;
			}
			
		}
		return fullExp !=null ? wrapType(fullExp, expectedType, model) : JExpr._null();
	}

	private JExpression wrapType(JExpression fullExp, Class<?> expectedType, CodeModelContainerModel model) {
		
		if(expectedType!=null && (Integer.class.equals(expectedType) || int.class.equals(expectedType))) {
			LOGGER.info("Attempting to wrap a {}" , expectedType.getName());
			//exp= JExpr.invoke(new JMethod(0, Integer ), exp );
			JClass ref = model.getCodeModel().ref(Integer.class);
				
			return ref.staticInvoke("parseInt").arg(fullExp);
		} else {
			return fullExp;
		}
	}
	

	public void loadProperties(String value) {
		Properties placeholderProperties= attemptToMergePlaceholderProperties(value,this.propertyPlaceholderResolution.getProperties());
		this.propertyPlaceholderResolution.setProperties(placeholderProperties);

	}
	

	private Properties attemptToMergePlaceholderProperties(String location, Properties placeholderProperties) {
		Properties returnProperties;
		if(placeholderProperties==null) {
			returnProperties= new Properties();
		} else {
			returnProperties = placeholderProperties;
		}
		LOGGER.info("merging properties with {}",location);
		Properties newProperties= attemptToLoadProperties(location);
		returnProperties.putAll(newProperties);
		return returnProperties;
	}
	

	private Properties attemptToLoadProperties(String location) {
		Properties newProperties= new Properties();
		try {
			newProperties.load(new FileInputStream(new File(location)));
			LOGGER.info("new properties: {}", newProperties);
		} catch (IOException e) {
			LOGGER.error("Exception loading properties file: "+location, e);
		}
		return newProperties;
	}
	
	public JExpression calcOrGuessExpressionFromString(String valueBefore,
			Class<?> expectedType, String designatedType, CodeModelContainerModel model) {
		JExpression injectedValue;
		
		//check if it's changed ...
		String valueReplaced = this.propertyPlaceholderResolution.resolveString(valueBefore);
		//if so, build it as a 'getter' 
		if(!valueBefore.equals(valueReplaced))
		{
			LOGGER.info("building from properties! val {}, type {}", valueBefore, expectedType);
			return buildPropertyGetterFromProperties(valueBefore, expectedType, model);
		}
		if(expectedType!=null) {
			injectedValue = injectByType(expectedType, valueReplaced);
		} else {
			
			//attempt to use designatedType
			Class<?> classFromClasspath = ClassLoaderUtil.getClassFromClasspath(designatedType);
			if(classFromClasspath!=null) {
				injectedValue = injectByType(classFromClasspath, valueReplaced);
			} else {
				injectedValue = guessTypeFromContent(valueReplaced);
			}
		}
		return injectedValue;
	}

	private JExpression injectByType(Class<?> expectedType, String valueReplaced) {
		JExpression injectedValue;
		SimpleTypeConverter simpleTypeConverter = new SimpleTypeConverter();
		Class<?> nonPrimitiveExpectedType = expectedType;
		if(expectedType.isPrimitive()) { //simplify with boxing ...
			nonPrimitiveExpectedType = JCodeModel.primitiveToBox.get(expectedType);
		}
		Object convertedIfNecessary = simpleTypeConverter.convertIfNecessary(valueReplaced, expectedType);
		
		if(Integer.class.equals(nonPrimitiveExpectedType)) {
			injectedValue= JExpr.lit( ((Integer) convertedIfNecessary).intValue() );
		} else if(Long.class.equals(nonPrimitiveExpectedType)) {
			injectedValue= JExpr.lit( ((Long) convertedIfNecessary).longValue() );
		} else if(Boolean.class.equals(nonPrimitiveExpectedType)) {
			injectedValue= JExpr.lit( ((Boolean) convertedIfNecessary).booleanValue() ); 
		} else if(Float.class.equals(nonPrimitiveExpectedType)) {
			injectedValue= JExpr.lit( ((Float) convertedIfNecessary).floatValue() );
		} else if(Double.class.equals(nonPrimitiveExpectedType)) {
			injectedValue= JExpr.lit( ((Double) convertedIfNecessary).doubleValue() );
		} else if(Character.class.equals(nonPrimitiveExpectedType)) {
			injectedValue= JExpr.lit( ((Character) convertedIfNecessary).charValue() );
		} else if(String.class.equals(nonPrimitiveExpectedType)) { //string overrides ..
			injectedValue= JExpr.lit( (String) convertedIfNecessary );
		} else if(TypedStringValue.class.isAssignableFrom(convertedIfNecessary.getClass())) {
			TypedStringValue converted= (TypedStringValue) convertedIfNecessary;
			injectedValue= JExpr.lit(converted.getValue());
		} else { 
			injectedValue = guessTypeFromContent(valueReplaced);
		}
		return injectedValue;
	}

	//TODO: booleans ...
	private JExpression guessTypeFromContent(String value) {
		try {
			return JExpr.lit(Float.valueOf(value));	
		} catch(NumberFormatException nfe) {
			try { 
				return JExpr.lit(Integer.valueOf(value));
			} catch(NumberFormatException nfe2) {
				return JExpr.lit( value.toString() );
			}
		}
		
	}
}
